def getDevicePort():
    import serial
    import serial.tools.list_ports
    ports_list = list(serial.tools.list_ports.comports())
    for comport in ports_list:
        if 'USB-SERIAL CH340' in comport[1] or 'USB Serial' in comport[1]:
            return comport[0]
    try:
        return ports_list[0][0]
    except:
        return None

def getDevice():
    import serial
    return serial.Serial(getDevicePort(),1000000)

def ModBusCRC16(cmd):
    cmd = bytearray.fromhex(cmd)
    CRC16 = 0xFFFF   #CRC寄存器初始值
    for byte in cmd:
        CRC16 ^= byte
        for j in range(8):
            if CRC16 & 0x0001:
                CRC16 >>= 1
                CRC16 ^= 0xA001
            else:
                CRC16 >>= 1
    return (CRC16<<8|CRC16>>8) & 0xFFFF;#低八位在前，高八位在后

def sweepMode(startV=-2000,
              endV=2000,
              channel = '01',
              points = 101,
              settlingTime = 100):
    seqStart = "FD"#帧头
    Type = "01"#类型
    DataLen = "00"#数据长度，自动计算无需修改
    Mode = "01" #工作模式2B 00返回心跳 01扫描模式 02定点模式 FF停止
    channel = channel #电流通道01或02
    startV = "{:04x}".format(startV & 0xFFFF)  #单位mV,改前边的数字
    endV = "{:04x}".format(endV & 0xFFFF)  #单位mV,改前边的数字
    step = "{:04x}".format(points & 0xFFFF) #步数！不是步长！
    settling = "{:04x}".format(settlingTime & 0xFFFF) #停留时间

    cmd=[seqStart,Type,DataLen,Mode,channel,startV,endV,step,settling]
    cmd[2] = "{:02x}".format(len("".join(cmd))//2)

    check = "{:04x}".format(ModBusCRC16("".join(cmd)))#校验码
    seqEnd = "F8"#帧尾
    cmd.append(check)
    cmd.append(seqEnd)
    return " ".join(cmd).upper()

def pointMode(SV1 = 0, SV2 = 0, SV3 = 0, SV4 = 0,
              points = 5,
              settlingTime = 10):
    seqStart = "FD"#帧头
    Type = "02"#类型
    DataLen = "00"#数据长度，自动计算无需修改
    Mode = "02" #工作模式2B 00返回心跳 01扫描模式 02定点模式 FF停止
    OUT = []
    for setV in [SV1,SV2,SV3,SV4]:#四路电压
        OUT.append("{:04x}".format(setV & 0xFFFF))
    channel = '0F' #0304电流通道 0506电压通道

    step = "{:04x}".format(points & 0xFFFF) #步数！不是步长！
    settling = "{:04x}".format(settlingTime & 0xFFFF) #停留时间

    cmd=[seqStart,Type,DataLen,Mode,*OUT,channel,step,settling]
    cmd[2] = "{:02x}".format(len("".join(cmd))//2)
    check = "{:04x}".format(ModBusCRC16("".join(cmd)))#校验码
    seqEnd = "F8"#帧尾
    cmd.append(check)
    cmd.append(seqEnd)

    return " ".join(cmd).upper()


def sendCmd2Device(cmd,gtamp2,sleep=1,prog=False):
    import time
    import binascii

    gtamp2.write(bytes.fromhex(cmd))
    while True:
        time.sleep(sleep)
        buf = str(binascii.b2a_hex(gtamp2.read(gtamp2.in_waiting)))
        buf = buf[2:-1]
        heart = ''
        datas = ''
        while buf:
            if buf.startswith('fda1'):
                heart = buf[:22]
                buf = buf[22:]
            elif buf.startswith('fda2'):
                dataCount = int(buf[6:14],16)
                lenData = dataCount*32+26
                datas = buf[:lenData]
                buf = buf[lenData:]
            else:
                break#如果是非协议输出则直接停止防死锁
        #进度条
        proBar = heart if heart[6:8] == '01' else None
        if proBar and prog:
            percent = int(proBar[10:12],16)
            print("\r",
                  " " if percent<100 else "",
                  "{:02d}%".format(percent),
                  '|'+'+'*(percent//4)+' '*(25-percent//4)+'| ',
                  end = "")
        #收到测试数据就停止
        if datas:
            result = datas
            break
        #未采集到数据而停止
        if heart:
            if heart[6:8] == '00':
                result = None
                break
    return result

def sendCmd(cmd,port = None,sleep=0.5,prog = True):
    if port == None:
        port = getDevicePort()
    import serial
    with serial.Serial(port,1000000) as gtamp2:
        return sendCmd2Device(cmd,gtamp2,sleep,prog)



def hex2list(strHex):
    Mode = int(strHex[5])-1
    stdUnit = [
        [1e-3, 1e-12, 1e-6, 1e-6],
        [1e-12, 1e-12, 1e-6, 1e-6]
        ]
    step = (len(strHex)-26)//32
    def hex2int32(inhex):
        rvs = inhex[6:8]+inhex[4:6]+inhex[2:4]+inhex[0:2]
        from ctypes import c_int32
        return c_int32(int(rvs,16)).value
    return [
        [
            hex2int32(strHex[20+(step*j+i)*8:20+(step*j+i+1)*8]) * stdUnit[Mode][j]
            for i in range(step)
            ] 
        for j in range(4)]

def hex2table(strHex,columns=['ch1','ch2','ch3','ch4']):
    import pandas as pd
    return pd.DataFrame(list(zip(*hex2list(strHex))),columns=columns)


def shutdown():
    sendCmd(pointMode(points=1,settlingTime=1),prog=False)
